package org.mountainpunch.ds.sparsematrix;

import java.util.LinkedList;

/**
 * Helper methods for Sparse matrices to perform arithmetic operations.
 * @author siddharth
 */
public class SparseMatrixUtils {

  public static <T extends MatrixDataOperable> SparseMatrix<T> add(SparseMatrix<T> amatrix,
          SparseMatrix<T> bmatrix) {
    if (null == amatrix || null == bmatrix) {
      throw new NullPointerException("Input matrice(s) is null");
    }

    if (amatrix.getNumRows() != bmatrix.getNumRows()
            || amatrix.getNumCols() != bmatrix.getNumCols()) {
      throw new RuntimeException("Invalid matrix operands");
    }

    LinkedList<MatrixEntry<T>> termsList = new LinkedList<MatrixEntry<T>>();

    SparseMatrixIterator<T> aiter = amatrix.iterator();
    SparseMatrixIterator<T> biter = bmatrix.iterator();
    SparseMatrixIterator<T> biggerIter = amatrix.size() > bmatrix.size() ? aiter : biter;
    SparseMatrixIterator<T> smallerIter = amatrix.size() < bmatrix.size() ? aiter : biter;

    MatrixDataOperable bop = null;
    MatrixDataOperable sop = null;

    while ((bop == null &&
          sop == null &&
          biggerIter.hasNext() == false &&
          smallerIter.hasNext() == false) == false) {

      if (bop == null) {
       if (biggerIter.hasNext()) {
          bop =  (MatrixDataOperable) biggerIter.next();
        }
      } else {
        bop = (MatrixDataOperable) biggerIter.current();
      }
      
      if (sop == null) {
        if (smallerIter.hasNext()) {
          sop = (MatrixDataOperable) smallerIter.next();
        }
      } else {
        sop = (MatrixDataOperable) smallerIter.current();
      }

      if (bop == null) {
        int srow = smallerIter.getRow();
        int scol = smallerIter.getCol();
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) sop);

        sop = null;
        termsList.add(matrixEntry);
        continue;
      } else if (sop == null) {
        int srow = biggerIter.getRow();
        int scol = biggerIter.getCol();
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) bop);

        bop = null;
        termsList.add(matrixEntry);
        continue;
      }
      
      int bcol = biggerIter.getCol();
      int brow = biggerIter.getRow();
      int scol = smallerIter.getCol();
      int srow = smallerIter.getRow();

      if (brow == srow && bcol == scol) {

        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) bop.add(sop));
        bop = sop = null;
        termsList.add(matrixEntry);

      } else if (brow < srow || bcol < scol) {
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(brow);
        matrixEntry.setCol(bcol);

        matrixEntry.setValue((T) bop);
        bop = null;

        termsList.add(matrixEntry);

      } else {
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) sop);

        sop = null;
        termsList.add(matrixEntry);
      }
    }
    System.out.println("# of entries = " + termsList.size());
    MatrixEntry<T>[] termsArr = emptyArray(termsList.size());
    termsArr = termsList.toArray(termsArr);

    return new SparseMatrix<T>(amatrix.getNumRows(), bmatrix.getNumCols(), termsArr);
  }

  public static <T extends MatrixDataOperable> SparseMatrix<T> subtract(SparseMatrix<T> amatrix,
          SparseMatrix<T> bmatrix) {
    if (null == amatrix || null == bmatrix) {
      throw new NullPointerException("Input matrice(s) is null");
    }

    if (amatrix.getNumRows() != bmatrix.getNumRows()
            || amatrix.getNumCols() != bmatrix.getNumCols()) {
      throw new RuntimeException("Invalid matrix operands");
    }

    LinkedList<MatrixEntry<T>> termsList = new LinkedList<MatrixEntry<T>>();

    SparseMatrixIterator<T> aiter = amatrix.iterator();
    SparseMatrixIterator<T> biter = bmatrix.iterator();
    SparseMatrixIterator<T> biggerIter = amatrix.size() > bmatrix.size() ? aiter : biter;
    SparseMatrixIterator<T> smallerIter = amatrix.size() < bmatrix.size() ? aiter : biter;

    MatrixDataOperable bop = null;
    MatrixDataOperable sop = null;

    while ((bop == null &&
          sop == null &&
          biggerIter.hasNext() == false &&
          smallerIter.hasNext() == false) == false) {

      if (bop == null) {
       if (biggerIter.hasNext()) {
          bop = (MatrixDataOperable) biggerIter.next();
        }
      } else {
        bop = (MatrixDataOperable) biggerIter.current();
      }

      if (sop == null) {
        if (smallerIter.hasNext()) {
          sop = (MatrixDataOperable) smallerIter.next();
        }
      } else {
        sop = (MatrixDataOperable) smallerIter.current();
      }

      if (bop == null) {
        int srow = smallerIter.getRow();
        int scol = smallerIter.getCol();
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) sop);

        sop = null;
        termsList.add(matrixEntry);
        continue;
      } else if (sop == null) {
        int srow = biggerIter.getRow();
        int scol = biggerIter.getCol();
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) bop);

        bop = null;
        termsList.add(matrixEntry);
        continue;
      }

      int bcol = biggerIter.getCol();
      int brow = biggerIter.getRow();
      int scol = smallerIter.getCol();
      int srow = smallerIter.getRow();

      if (brow == srow && bcol == scol) {

        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) bop.subtract(sop));
        bop = sop = null;
        termsList.add(matrixEntry);

      } else if (brow < srow || bcol < scol) {
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(brow);
        matrixEntry.setCol(bcol);

        matrixEntry.setValue((T) bop);
        bop = null;

        termsList.add(matrixEntry);

      } else {
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) sop.negate());

        sop = null;
        termsList.add(matrixEntry);
      }
    }
    System.out.println("# of entries = " + termsList.size());
    MatrixEntry<T>[] termsArr = emptyArray(termsList.size());
    termsArr = termsList.toArray(termsArr);

    return new SparseMatrix<T>(amatrix.getNumRows(), bmatrix.getNumCols(), termsArr);
  }

  public static <T extends MatrixDataOperable> SparseMatrix<T> interpolate(SparseMatrix<T> amatrix,
          SparseMatrix<T> bmatrix, float t) {
    if (null == amatrix || null == bmatrix) {
      throw new NullPointerException("Input matrice(s) is null");
    }

    if (amatrix.getNumRows() != bmatrix.getNumRows()
            || amatrix.getNumCols() != bmatrix.getNumCols()) {
      throw new RuntimeException("Invalid matrix operands");
    }

    LinkedList<MatrixEntry<T>> termsList = new LinkedList<MatrixEntry<T>>();

    SparseMatrixIterator<T> aiter = amatrix.iterator();
    SparseMatrixIterator<T> biter = bmatrix.iterator();
    SparseMatrixIterator<T> biggerIter = amatrix.size() > bmatrix.size() ? aiter : biter;
    SparseMatrixIterator<T> smallerIter = amatrix.size() < bmatrix.size() ? aiter : biter;

    MatrixDataOperable bop = null;
    MatrixDataOperable sop = null;

    while ((bop == null &&
          sop == null &&
          biggerIter.hasNext() == false &&
          smallerIter.hasNext() == false) == false) {

      if (bop == null) {
       if (biggerIter.hasNext()) {
          bop = (MatrixDataOperable) biggerIter.next();
        }
      } else {
        bop = (MatrixDataOperable) biggerIter.current();
      }

      if (sop == null) {
        if (smallerIter.hasNext()) {
          sop = (MatrixDataOperable) smallerIter.next();
        }
      } else {
        sop = (MatrixDataOperable) smallerIter.current();
      }

      if (bop == null) {
        int srow = smallerIter.getRow();
        int scol = smallerIter.getCol();
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) sop);

        sop = null;
        termsList.add(matrixEntry);
        continue;
      } else if (sop == null) {
        int srow = biggerIter.getRow();
        int scol = biggerIter.getCol();
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) bop);

        bop = null;
        termsList.add(matrixEntry);
        continue;
      }

      int bcol = biggerIter.getCol();
      int brow = biggerIter.getRow();
      int scol = smallerIter.getCol();
      int srow = smallerIter.getRow();

      if (brow == srow && bcol == scol) {

        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) bop.interpolate(sop, t));
        bop = sop = null;
        termsList.add(matrixEntry);

      } else if (brow < srow || bcol < scol) {
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(brow);
        matrixEntry.setCol(bcol);

        matrixEntry.setValue((T) bop);
        bop = null;

        termsList.add(matrixEntry);

      } else {
        MatrixEntry<T> matrixEntry = new MatrixEntry<T>();
        matrixEntry.setRow(srow);
        matrixEntry.setCol(scol);

        matrixEntry.setValue((T) sop);

        sop = null;
        termsList.add(matrixEntry);
      }
    }
    System.out.println("# of entries = " + termsList.size());
    MatrixEntry<T>[] termsArr = emptyArray(termsList.size());
    termsArr = termsList.toArray(termsArr);

    return new SparseMatrix<T>(amatrix.getNumRows(), bmatrix.getNumCols(), termsArr);
  }



  /**
   * Creates an empty array of MatrixEntries.
   * @param capacity the number of elements in the array object.
   * @return the parameterized array object.
   */
  private static <T> MatrixEntry<T>[] emptyArray(int capacity) {
    MatrixEntry<T>[] matArr;
    matArr = (MatrixEntry<T>[]) java.lang.reflect.Array.newInstance(MatrixEntry.class, capacity);
    for (int i = 0; i < matArr.length; i++) {
      matArr[i] = new MatrixEntry<T>();
    }
    return matArr;
  }
//  private boolean storeSum(int sum, int lastInResult, int r, int c) {
//    if (sum != 0) {
//      if (lastInResult < _numTerms + 1) {
//        lastInResult++;
//        _terms[lastInResult] = new MatrixEntry();
//        _terms[lastInResult].setRow(r);
//        _terms[lastInResult].setCol(c);
//        _terms[lastInResult].setValue(sum);
//        return false;
//      } else {
//        throw new RuntimeException("Number of terms in product exceeds MaxTerms");
////        System.err.println("Number of terms in product exceeds MaxTerms");
////        return false;
//      }
//    }
//
//    return false;
//  }
//
//  public SparseMatrix multiply(SparseMatrix b) {
//    if (_cols != b.getNumRows()) {
//      System.err.println("Incompatible matrices");
//      return new SparseMatrix();
//    }
//
//    SparseMatrix btranspose = b.fastTranspose();
//    SparseMatrix result = new SparseMatrix(_rows, b.getNumCols());
//    int currRowIndex = 0;
//    int lastInResult = -1;
//    int currRowBegin = 0;
//    int currRowA = _terms[0].getRow();
//
//    //set boundary conditions.
//    this._terms[_numTerms].setRow(_rows);
//    MatrixEntry[] bterms = btranspose.getTerms();
//    bterms[b.getNumTerms()].setRow(b.getNumCols());
//    bterms[b.getNumTerms()].setCol(-1);
//    int sum = 0;
//
//    while (currRowIndex < _numTerms) { //generate row currRowA of result.
//      int currColB = bterms[0].getRow();
//      int currColIndex = 0;
//      while (currColIndex <= b.getNumTerms()) {
//        //multiply row currRowA if a by column currColB of b.
//
//        //end of row CurrRowAnew SparseMatrix()
//        if (_terms[currRowIndex].getRow() != currRowA) {
//          if (result.storeSum(sum, lastInResult, currRowA, currColB)) {
//            return new SparseMatrix();
//          } else {
//            sum = 0;
//          }
//          currRowIndex = currRowBegin;
//          //go to the next column.
//          while (bterms[currColIndex].getRow() == currColB) {
//            currColIndex++;
//          }
//          currColB = bterms[currColIndex].getRow();
//        } else if (bterms[currColIndex].getRow() != currColB) {
//          //end of column currColB.
//          if (result.storeSum(sum, lastInResult, currRowA, currColB)) {
//            return new SparseMatrix();
//          } else {
//            sum = 0;
//          }
//          //Set to multiply row currRowA with next column.
//          currRowIndex = currRowBegin;
//          currColB = bterms[currColIndex].getRow();
//        } else {
//          int acol = _terms[currRowIndex].getCol();
//          int bcol = bterms[currColIndex].getCol();
//          if (acol < bcol) {
//            currRowIndex++;
//            break;
//
//          } else if (acol == bcol) {
//            sum += _terms[currRowIndex].getValue() * bterms[currColIndex].getValue();
//            currRowIndex++;
//            currColIndex++;
//            break;
//          } else if (acol > bcol) {
//            currColIndex++;
//          }
//        }
//      } //end of while(currColIndex <= b.terms)
//      while (_terms[currRowIndex].getRow() == currRowA) {
//        //advance to next row.
//        currRowIndex++;
//      }
//      currRowBegin = currRowIndex;
//      currRowA = _terms[currRowIndex].getRow();
//    } //end of while(currRowIndex < terms)
//    result._rows = _rows;
//    result._cols = _cols;
//    result._numTerms = lastInResult + 1;
//    return result;
//  }
}
